// Copyright 2017 Baidu Inc. All Rights Reserved.
// Author: Duan Lian (duanlian01@baidu.com)
//
// Description: OTA Unpack Tool

#include "baidu_iot_ota_unpack_tool.h"
#include <string.h>
#include <stdlib.h>
#include "rtos.h"
#include "duer_log.h"
#include "package_api.h"
#include "pack_include.h"
#include "verification.h"
#include "heap_monitor.h"
#include "baidu_iot_ota_log.h"
#include "baidu_iot_ota_reporter.h"

namespace duer {

static const float PERCENT_REPORT_INTERVAL = 0.1;

NewFirmwareImageUnpackTool::NewFirmwareImageUnpackTool(unsigned int decompress_chunk_size,
                                                       unsigned int resource_size,
                                                       IOtaUpdater* updater,
                                                       void* update_cxt,
                                                       OTAReporter* reporter) {
    // TODO Auto-generated constructor stub
    this->_resource_size = resource_size;
    this->_resource_offset = 0;
    this->_download_percent = 0;
    _chunk_size = decompress_chunk_size;

    if (_chunk_size > 0) {
        _cache = (unsigned char*)MALLOC(_chunk_size, OTA);
        _in_size = 0;
    }

    _verification_cxt = 0;
    _decompress_ctx = 0;
    this->_updater = updater;
    this->_update_ctx = update_cxt;
    this->_m_p_reporter = reporter;
}

NewFirmwareImageUnpackTool::~NewFirmwareImageUnpackTool() {
    // TODO Auto-generated destructor stub
    if (_cache) {
        FREE(_cache);
        _cache = NULL;
    }
}

int NewFirmwareImageUnpackTool::unpack_ota_package_begin() {
    int status = 0;
    this->_resource_offset = 0;
    this->_download_percent = 0;

    // Check if chunk size is valid or not
    if (_chunk_size <= 0) {
        status = 1;

        goto out;
    }

    _verification_cxt = mbed_verification_init();

    if (!_verification_cxt) {
        DUER_LOGI("Receive_ota_package_begin : _verification_cxt is NULL...");
        status = 2;

        goto out;
    }

    _decompress_ctx = mbed_decompress_init();

    if (!_decompress_ctx) {
        DUER_LOGI("Receive_ota_package_begin : decompress_cxt is NULL...");
        status = 3;

        goto out;
    }

    if (_updater && _updater->notify_data_begin) {
        if (_updater->notify_data_begin() != 0) {
            status = 4;

            goto out;
        }
    }

out:
    return status;
}

// 0 on success, 1 on decompress failure
int NewFirmwareImageUnpackTool::unpack_ota_package(unsigned char* data, unsigned int len) {
    int status = -1;
    unsigned int offset = 0;

    if (!data) {
        DUER_LOGE("Arguments invalid");

        return 1;
    }

    while (offset < len) {
        if ((_chunk_size - _in_size) >= (len - offset)) {
            memcpy(_cache + _in_size, data + offset, len - offset);
            _in_size += (len - offset);
            offset = len;
            DUER_LOGV("Receive_ota_package_data cache data");
            status = 0;
        } else {
            memcpy(_cache + _in_size, data + offset, _chunk_size - _in_size);
            offset += (_chunk_size - _in_size);
            _in_size = _chunk_size;
            DUER_LOGV("Receive_ota_package_data decompress");
            mbed_verification_update_ctx(_verification_cxt, _cache, _chunk_size);

            if (mbed_decompress_process(_verification_cxt, _decompress_ctx, _cache, _chunk_size,
                                        _updater, _update_ctx) != 0) {
                DUER_LOGV("Receive_ota_package_data : decompress failed, exit...");
                _m_p_reporter->report_event(OTAReporter::OTA_EVENT_IMAGE_INVALID);
                status = 1;

                return status;
            }

            _in_size = 0;
            status = 0;
        }
    }

    this->_resource_offset += len;
    double db_cur_percent = this->_resource_offset * 1.0 / this->_resource_size;

    if ((db_cur_percent - this->_download_percent) > PERCENT_REPORT_INTERVAL) {
        _m_p_reporter->report_event(OTAReporter::OTA_EVENT_DOWNLOADING, db_cur_percent);
        this->_download_percent = db_cur_percent;
    }

    status = 0;

    return status;
}

int NewFirmwareImageUnpackTool::unpack_ota_package_end() {
    int status = 0;

    OTA_FUNC_ENTER();

    double db_cur_percent = this->_resource_offset * 1.0 / this->_resource_size;
    _m_p_reporter->report_event(OTAReporter::OTA_EVENT_DOWNLOADING, db_cur_percent);
    // Give the time to send the above report message(this thread have a higher priority than bca)
    Thread::wait(1000);
    this->_download_percent = db_cur_percent;
    DUER_LOGD("Unpack OTA package end : _resource_offset=%d, _resource_size=%d",
                _resource_offset, _resource_size);

    if (this->_resource_offset == this->_resource_size) {
        _m_p_reporter->report_event(OTAReporter::OTA_EVENT_DOWNLOAD_COMPLETE);
        /*
         * Give the time to send the above report message
         * This thread have a higher priority than bca
         */
        Thread::wait(1000);
        _m_p_reporter->report_event(OTAReporter::OTA_EVENT_INSTALLING);
        /*
         * Give the time to send the above report message
         * this thread have a higher priority than bca
         */
        Thread::wait(1000);
        mbed_verification_update_ctx(_verification_cxt, _cache, _in_size);

        if (mbed_verification(_verification_cxt) != 0) {
            DUER_LOGE("Receive_ota_package_end : verify failed, exit...r");
            _m_p_reporter->report_event(OTAReporter::OTA_EVENT_IMAGE_INVALID);
            return status = 1;
        } else {
            DUER_LOGI("Receive_ota_package_end : verify success, go on...");
        }

        if (mbed_decompress_process(_verification_cxt, _decompress_ctx, _cache, _in_size,
                                    _updater, _update_ctx) != 0) {
            DUER_LOGE("Receive_ota_package_end : decompress failed, exit....");
            _m_p_reporter->report_event(OTAReporter::OTA_EVENT_IMAGE_INVALID);
            return status = 2;
        } else {
            DUER_LOGI("Receive_ota_package_end : decompress success, go on...");
        }

        status = 0;
        mbed_decompress_uninit(_decompress_ctx);
    } else {
        DUER_LOGE("Unpack_ota_package_end : _resource_offset=%d, _resource_size=%d",
                    _resource_offset, _resource_size);
        _m_p_reporter->report_event(OTAReporter::OTA_EVENT_DOWNLOAD_FAIL);
        /*
         * Give the time to send the above report message
         * This thread have a higher priority than bca
         */
        Thread::wait(1000);
        status = 3;

        return status;
    }

    _in_size = 0;

    if (_updater && _updater->notify_data_end) {
        if (_updater->notify_data_end() != 0) {
            status = 4;

            return status;
        }
    }

    OTA_FUNC_LEAVE();

    return status = 0;
}

int NewFirmwareImageUnpackTool::update_image_begin() {
    if (_updater && _updater->update_img_begin) {
        _updater->update_img_begin();
    }

    return 0;
}

int NewFirmwareImageUnpackTool::update_image() {
    int ret = 0;
    ret = this->_updater->update_img();

    if (0 == ret) {
        _m_p_reporter->report_event(OTAReporter::OTA_EVENT_INSTALLED);
        /*
         * Give the time to send the above report message
         * this thread have a higher priority than bca
         */
        Thread::wait(500);
    } else {
        _m_p_reporter->report_event(OTAReporter::OTA_EVENT_WRITE_ERROR);
        /*
         * Give the time to send the above report message
         * this thread have a higher priority than bca
         */
        Thread::wait(500);
    }

    return ret;
}

int NewFirmwareImageUnpackTool::update_image_end() {
    if (_updater && _updater->update_img_end) {
        _updater->update_img_end();
    }

    return 0;
}

} // namespace duer
